package cn.com.bsoft.mobile.service


import cn.com.bsoft.mobile.dao.MobileArchiveDao
import cn.com.bsoft.mobile.dao.VersionDao
import cn.com.bsoft.mobile.entity.MobileAppVersion
import cn.com.bsoft.mobile.entity.MobileArchive
import cn.com.bsoft.mobile.model.*
import cn.com.bsoft.mobile.utils.IPAReader
import cn.net.xyan.blossom.core.service.ConfigService
import cn.net.xyan.blossom.core.utils.absoluteURL
import cn.net.xyan.blossom.core.utils.pageable
import cn.net.xyan.blossom.kotlin.AllOpen
import cn.net.xyan.blossom.kotlin.StatusAndMessageError

import com.dd.plist.NSArray
import com.dd.plist.NSDictionary
import com.dd.plist.NSObject
import com.vaadin.ui.Upload
import net.dongliu.apk.parser.ByteArrayApkFile
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Sort
import org.springframework.transaction.annotation.Transactional
import java.io.ByteArrayInputStream
import java.io.InputStream


interface AppVersionService {
    fun queryVersion(req: QueryAppVersionReq): List<AppVersionRsp>

    fun queryVersion(type: String, appID: String, version: String): MobileAppVersion?

    fun queryVersion(appID: String,type:String?): List<AppVersionRsp>

    fun submitVersion(req: SubmitAppVersionReq): MobileAppVersion

    fun submitVersion(fileName:String,mimeType:String, inputStream: InputStream,describe:String? = null): MobileAppVersion

    fun toggleVersion(type: String, appID: String, version: String):MobileAppVersion

    fun removeVersion(type: String, appID: String, version: String):MobileAppVersion

    fun convert2plist(version:MobileAppVersion):NSObject
}

interface AppVersionUploadReceiver:Upload.Receiver, Upload.SucceededListener

fun MobileAppVersion.toRsp(): AppVersionRsp {
    val rsp = AppVersionRsp()

    rsp.appID = this.app.nativeID
    rsp.version = this.version
    rsp.describe = this.describe
    rsp.createDate = this.createDate
    rsp.enable = this.enable
    rsp.type = DeviceType.fromString(this.type)
    rsp.url = absoluteURL("/app/version/${this.type}/${rsp.appID}/${rsp.version}")
    rsp.appURL = absoluteURL("/app/inHouse/${rsp.appID}")
    rsp.archiveLength = this.archive?.data?.size?.toLong()
    return rsp
}

@AllOpen
class DefaultAppVersionService : AppVersionService {

    @Autowired
    lateinit var appService: MobileAppService

    @Autowired
    lateinit var configService: ConfigService

    @Autowired
    lateinit var versionDao: VersionDao

    @Autowired
    lateinit var archiveDao:MobileArchiveDao

    override fun queryVersion(req: QueryAppVersionReq): List<AppVersionRsp> {
        val app = appService.queryApp(req.nativeID ?: throw StatusAndMessageError("应用id 不能为null"))
                ?: throw StatusAndMessageError("未能找到App:${req.nativeID}}")

        val pageRequest = Sort.Direction.DESC.pageable(MobileAppVersion::createDate.name, req.page?:0, req.pageSize?:1)

        val result = if (req.version == null) {
            versionDao.queryAvailableVersion(app, app.type, pageRequest)
        }else{
            versionDao.queryVersion(app,app.type,req.version!!,pageRequest)
        }

        return result.map {
            val rsp = it.toRsp()
            rsp
        }

    }

    override fun queryVersion(type: String, appID: String, version: String): MobileAppVersion? {
        val app = appService.queryApp(appID) ?: throw StatusAndMessageError("未发现appID:$appID")
        val result = versionDao.queryVersion(app, type, version)
        if (result.isEmpty()) {
            throw StatusAndMessageError("未发现 version:$version")
        } else {
            return result.first()
        }
    }

    override fun queryVersion(appID: String, type: String?): List<AppVersionRsp> {

        val result = if (type == null){
            versionDao.queryVersion(appID)
        }else{
            versionDao.queryVersion(appID,type)
        }

        return result.sortedWith(compareBy(MobileAppVersion::createDate)).map {
            it.toRsp()
        }
    }

    @Transactional
    override fun toggleVersion(type: String, appID: String, version: String): MobileAppVersion {
        val v = queryVersion(type,appID,version)?:throw StatusAndMessageError("未找到版本 $version")
        v.enable = !v.enable

        versionDao.updateEnable(v.enable,v)

        return v

    }

    @Transactional
    override fun removeVersion(type: String, appID: String, version: String): MobileAppVersion {
        val v = queryVersion(type,appID,version)?:throw StatusAndMessageError("未找到版本 $version")
        val archive = v.archive



        v.archive = null

        versionDao.delete(v)

        archiveDao.delete(archive)

        return v
    }

    @Transactional
    override fun submitVersion(req: SubmitAppVersionReq): MobileAppVersion {

        val appReq = App(req.nativeID!!,req.type!!,req.title)

        val app = appService.setupApp(appReq)

        var appVersion = MobileAppVersion(app, req.type!!.value)

        var archive = MobileArchive()

        archive.data = req.data

        archive = archiveDao.saveAndFlush(archive)

        appVersion.archive = archive
        appVersion.describe = req.describe
        appVersion.enable = req.enable
        appVersion.version = req.version

        appVersion = versionDao.saveAndFlush(appVersion)

        return appVersion
    }

    override fun submitVersion(fileName: String, mimeType: String, inputStream: InputStream, describe: String?): MobileAppVersion {
        var appVersion = when {
            fileName.endsWith(".apk") -> submitVersionAndroid(inputStream)
            fileName.endsWith(".ipa") -> submitVersionIOS(inputStream)
            else -> throw StatusAndMessageError("不支持文件类型:$mimeType")
        }

        appVersion.describe = describe

        appVersion = versionDao.saveAndFlush(appVersion)

        return appVersion
    }

    private fun submitVersionAndroid(inputStream: InputStream): MobileAppVersion {
        inputStream.use {
            input ->
            val req = SubmitAppVersionReq()
            req.type = DeviceType.Android

            val data = input.readBytes()

            val apkFile = ByteArrayApkFile(data)
            val apkMeta = apkFile.apkMeta

            req.nativeID = apkMeta.packageName
            req.version = apkMeta.versionName
            req.title = apkMeta.name
            req.data = data

            return submitVersion(req)
        }
    }

    private fun submitVersionIOS(inputStream: InputStream): MobileAppVersion {
        inputStream.use {
            val data = inputStream.readBytes()
            val input = ByteArrayInputStream(data)
            val ipaReader = IPAReader(input)
            val ipaInfo = ipaReader.parse()

            val req = SubmitAppVersionReq()
            req.type = DeviceType.iOS

            req.nativeID = ipaInfo?.bundleIdentifier
            req.version = "${ipaInfo?.bundleVersionString}.${ipaInfo?.buildNumber}"
            req.title = ipaInfo?.bundleName
            req.data = data

            return submitVersion(req)
        }
    }

    //------------------------------------------------------------------------

    override fun convert2plist(version: MobileAppVersion): NSObject {
        val root = NSDictionary()
        val item = NSDictionary()

        val assets = NSDictionary()

        val appRoot = /*appRootURL()?:*/configService.queryValue(ConfigService.KEYAppRoot)

        assets.put("kind","software-package")
        assets.put("url","$appRoot/app/version/${DeviceType.iOS.value}/${version.app.nativeID}/${version.version}")

        val metadata = NSDictionary()

        metadata.put("bundle-identifier",version.app.nativeID)
        metadata.put("bundle-version",version.version)
        metadata.put("kind","software")
        metadata.put("title",version.app.title)

        item.put("assets", NSArray(assets))
        item.put("metadata",metadata)

        val items = NSArray(item)

        root.put("items",items)

        return root
    }

}